The Ruby Language
A new player in the arena
Introduction.
Computer science is one of our main interests. Each of us deals with it in a different manner but every one of us needs to be aware of all the major events that bring some sort of new feelings in the game and that bring with them some new and smarter ways of solving our everyday problems. In the programming field one of these new tool is Ruby. It is a brand new language and it goes like this.
Fashion or not fashion?
Programming and computer science in general deal with fashion and hype. They are all about speed and "brand new and terrifying kicking ass products" every day and most of them end up totally forgotten in your company garbage near your last bottles of coke.
It is part of our job to sort all this mess and pick up reliable and well-done things that would enhance our productivity and allow us to produce better software. Part of our task is to keep our eyes opened on the world and on its new products and technologies. Ruby is part of that new fancy stuff and I think it really deserves some time to get interested in it.
What is that damned stuff called Ruby?
And the answer is: a new interpreted Python-like programming language. Like Python, it is an Object Oriented (OO) one and includes a lot of interesting features which you could find in some other languages like Smalltalk, C++, Java, etc...
It was created by a Japanese guy named Yukihiro Matsumoto alias "Matz" and first released in 1993. He gave birth to this in order to propose his own OO alternative to Perl and Python programming. Perl is definitely not an OO language and Python did not propose an alternative that was efficient and clear enough for him.
He wanted to have a fully OO programming language with heavy dynamic properties and a very clear syntax. And when I mean dynamic I do mean it. He added some very interesting features such as reflexivity, on-the-fly class redefinitions, run-time variable type determination (dynamically typed), garbage collector, etc... capabilities.
And one of the most interesting points that make this new programming language "fun" (and productivity-compliant) is its very intuitive and nice syntax. Along with this syntax, Matz wanted all this language to rely on one simple rule "The Principle Of Least Surprise". It really means what it says, the language is designed (and succeeds most of the time in this) so that there is no relative distance between an idea or an algorithm that you want to implement and the way to express it in terms of Ruby language. Everything is very natural or tries to be.
And one other important goal was that he wanted Ruby programming to be "fun" and pleasant, which I think he achieved quite well.
Let's be practical.
Here comes the time to see what this new stuff has in its guts. There are way too many things to say about it and I cannot be exhaustive in this simple enumeration.
The main point to understand in this language is that everything is an object. I really insists on the EVERYTHING stuff. For example, every literal number or string is an object to which you can apply all its interface methods:
A literal string is transformed to an integer (you apply the method to_i with no parameters to the literal string "1976"):
"1976".to_i()
A literal number is transformed to a string (you apply the method to_s with no parameters to the literal number 3):
3.to_s()
An interesting point, is that the Ruby interpreter allows you drop some parts of an expression when there is no ambiguity in your demands. For example, in the two upper Ruby lines I could have dropped the '()' part because there is obviously no need to keep them since there are no parameters in our method call.
This fully-OO stuff can be applied to anything and in particular to any of the built-in types that are in fact classes. Like Python, it supports: Arrays, Hashes (associative arrays), Small numbers, Big numbers, Boolean, Regular expressions, IO objects, Strings, Built-in Threads, Symbols,... Moreover, Ruby comes with a large Web library that features CGI, FTP, Telnet, TCP, UDP,... classes.
To be a little bit more pragmatic and to show another example of syntax, Ruby supports a very interesting and helpful feature called blocks. To make things simple, blocks are portions of code that come between a 'do' .. 'end' statement or between '{' and '}'. You can manipulate these blocks as you want, they are literally portions of a script that you can plug everywhere.
You can use these blocks, for example, as method parameters:
6.upto(10) { |i|
print i
}
Hey, let's split this stuff a little bit to understand how it works.
First, in Ruby there is no need of semi-colon, like in C for example. Semi-colons are only used when you put multiple instructions in one single line.
Second, you have the '6.upto(10)' part that applies the upto() method to the '6' integer class object. As we would expect, the upto() method parameter (10 in this case) indicates the upper limit of the desired count action.
Here is another possible syntax that would make things a little bit clearer regarding the purpose of this piece of code:
6.upto 10 do |i|
print i
end
One important thing that Ruby allows you to do is to put away the parenthesis in a method's parameters list if there is no ambiguity. So this piece of code is still valid and might now appear a little bit more clear: you just have to read it!!
"Count from 6 to 10 and, for each count, do this". Here is what it means. Simply take some time to compare the literal expression of our desire and its Ruby translation. Pretty clean no?
So now comes the problem of the '|i|' expression. This corresponds to one block parameter. In this case, the method upto() directly translates the programmer's will to count from 6 to 10. Usually, when you write such lines, it probably means that you want to perform an action a certain number of times. The block, here, is a way to directly give the upto() method the portion of code that you want to execute. The upto() method, internally, calls your block for every loop count.
The problem is that you might want to have access to the loop count. You can get this parameter for each call to your block through this '|i|'. You put all block parameters between '|' , at the beginning of your block, separating each parameter by a comma.
It tells Ruby "Well, if you have any parameters for my block I want it to be called 'i' in every loop". If you do not need this parameter, well,... just drop it! Why bother, the language takes care of it for you.
A taste of Ruby classes.
You can of course build you own classes with this Ruby stuff and let's give it a very quick try. Here is one simple ConfigFileParser class that is used to simply extract some relevant information from a text file. This text file should be formatted as lines of "[InitializationElementName] = value" expressions. Here is a very simple and not error-safe Ruby script I wrote for this example (the keywords are in blue):
class ConfigFileParser
def initialize( filename )
@filename = filename
File.open(@filename, "r"){ |file|
file.each_line { |line|
key, value = parseLine(line)
if key && value && block_given?
yield key, value
end
}
}
end
def parseLine(line)
if line =~ /^\[([^\[\]]*)\]\s*=\s*(.*)/
return $1, $2
end
return nil, nil
end
end
ConfigFileParser.new("test.txt") { |key, value| puts "key: #{key}, value = #{value}" }
Hey, WTF is that stuff! Yes I admit that it is quite rough as an introduction to Ruby classes/syntax but it contains a good amount of interesting features.
First let's see the basic syntax of a class definition:
class MyClass
end
Here it is! The keywords class and end respectively open and close a class definition scope. The name of a class must start with an upper case character as the Ruby interpreter parser uses that kind of name convention to interpret the source file. In fact it uses a couple of other conventions related to method names, constant names, etc... Do not get frightened by this, as these conventions are rather light and you get used to them very quickly.
Ruby allows some sort of a "constructor" for its classes. This constructor ALWAYS has the name "initialize". If you define a method in your class that has this name Ruby will automatically call it during the object creation phase. In this example, we use this constructor to initialize (!!) our internal instance variables. Once again, it is very important to understand the Ruby vocabulary here. If we compare this to C++ , an instance method or variable is a "simple" class method/variable in C++. A class method or variable can be compared to static ones in C++.
We define instance methods by using the keywords def when the definitions starts and end when the definition finishes.
We define instance variables by adding a '@' sign in front of their names.
So in the previous source, we define an instance variable (@filename) and an instance method (parseLine) in this class.
The class is structured so that we immediately try to open and parse the file in the constructor. The built-in class File is used here. Once the file has been opened, the instance method parseLine() is called for each file line and tries to extract the key element and its value from it.
An important point here is that we use blocks (as we have seen before) with the method open() (i.e. a static one) of the File class. This method accepts blocks. It tries to open the current file and simply calls the block with the opened file as a parameter if the open action succeeded. A very important point here is that when you get out of the block, the open method takes care of closing the file for you.
Before leaving this example aside, I would like to explain two particular points. The first is the use of the return keyword. return has the same meaning as in C/C++, it simply returns the given value. As Ruby is quite versatile, it allows you to omit the use of return and then just returns the value of the last expression evaluated in the method body. But here, we use a particular construction of the keyword return that features parallel assignment. In this particular case, I want my parseLine() instance method to return both the key and its value (which is extracted from a line with a very simple regular expression). Ruby allows me to return both of them separated by a comma. In this case, following the Principle Of Least Surprise, I am able to get back this value simply using the same contruction:
key, value = parseLine(line)
If there are too many receiver variables Ruby fills the extra ones with a nil value. The second point to which I wanted to come back is the fact that we allow the user to add a block to the constructors parameters list.
The line:
ConfigFileParser.new("test.txt") {|key, value| print "key: #{key}, value = #{value}\n"}
means that we instanciate a new ConfigFileParser class with a first argument "test.txt" and a second argument of type block that takes two possible parameters. This block can be called by the constructor at any moment and, if so, receives two arguments key and value. It just prints the values of its arguments. The constructor can check, at any moment, the existence of a block parameter by calling the method block_given?() that returns true is a block have been given to the contructor. If so, it can call this block by calling the yield method with the appropriate parameters!!
So here it is for this example. I cannot go further on this because this text is not meant to be a full introduction, it's just a simple "curiosity awakener" one.
Advanced Ruby.
Ruby is a very dynamic language. You can redefine everything at runtime, even your own source code! You can easily get an enormous amount of information on the running context that would allow to do crazy things like runtime auto-debugging and correction, etc...
You have full ability to inspect the current context and to get information about all the objects currently existing, about your own object class hierarchy, about the content of objects (methods and variables) that can of course be modified (runtime method redefinition), etc...
You also have the ability to trace your program call-stack at runtime. You then have access to the information of who is calling what.
Ruby also supports built-in marshalling (object serialization). You find this technique in more and more environments now (Windows ATL for example adds supports COM objects marshalling).
I cannot talk about every single of Ruby's features; there are plenty others. But I am pretty sure that you get the idea.
But I do not resist to give you a slight example of how Ruby could let you handle and solve a problem in a clever and interesting way. Let's say that you have some sort of a server that is able to respond correctly to a certain amount of requests. Each request takes the shape of one or several words that summarize each action. For example (a very stupid one), you may want to ask the server about the current time.
In that case, the words "Give time" might be sent to the server for example. A simple implementation of the message processing on the server side in C might deal with function pointers associated with strings together in a structure or maybe, in C++, some sort of STL std::map associating words to static methods (bad solution though).
In Ruby a solution description might look like this one:
1. Format the string in a more compact way (replace the ' ' by '_', etc...),
2. Simply use the string as a method name and try to call it,
3. If this method does not exists, Ruby tell it to us and we do what is supposed to be done in such cases,
In a source file, it would look like this (here the request string is 'requestStr'):
# remove all annoying characters (non alphanumeric ones plus '_') so that the string
# might be considered as a valid method name
requestStr.gsub!( /[^\w]/, '_' )
# try to call the method corresponding to the requestStr name (loosely)
self.send(requestStr)
If this method does not exist, Ruby tries to find and call a method of our class named 'method_missing'. So we just have to implement such a method to handle invalid requests:
def method_missing(methodId)
puts "Invalid call to method : #{methodId.id2name}"
# we may here raise an exception to inform the user that something went wrong
raise "Invalid call to method : #{methodId.id2name}"
end
I really hope that I haven't repelled you with such rough explanations of this kind of behaviour. Once again, I hope you get the idea.
Target platforms.
The Ruby interpreter is written in plain C language and is an open source project that is currently in its 1.6.6 version. It is highly portable and therefore exists on a great number of plateforms: UNIX, DOS, Windows 95/98/NT, Mac, BeOS, OS/2, etc.
I have even compiled and tried it under QNX 6.0!
You can easily extend Ruby using a C API. You can therefore add your own custom support for any of your tools, project, specific points,...
Useful for what?
Ruby is not a fancy, useless new fashion that floats around here. It is really worth spending time learning it. It can be used in a great number of areas.
I personally use it as a prototyping language to enhance my productivity and test some of my ideas. I also use it as many areas where C/C++ is not required.
Regarding graphics, Ruby supports a lot of libraries that extend its features in this area. It has an OpenGL library and a SDL one, some very useful image reader,... If you add the support of many GUI frameworks such as GTK+, Tk, FOX, win32 SDK... that allows you to really speed up your application development process, you have a perfect tool to build very interesting graphic related applications (various world editors, renderers...).
Ruby can do almost everything and you will be able to produce an interesting result in very little time. It can also be used as a glue between different and heterogeneous parts of a project.
And another important, but less obvious reason why Ruby is worth learning, is that it is a wonderful educational tool. It features very advanced stuff to which you have a very simple and intuitive access. It enables you to understand and experiment a lot of important OO and programming language related points.
There is no barrier between you and the language, you can expriment and implement with an incredible ease very complicated/strange/crazy stuff with all these dynamic features, there is no obstacle that would prevent you from easily coming up to a direct implementation of your ideas. The language is not running against you but it tries to take any shape you might imagine.
Despite such compliments towards Ruby, we must not forget that every language has its strengths and weaknesses. Our task is to know about both of them to be able to choose the most appropriate one in front of a problem.
First, Ruby is interpreted and does not appear (in this current interpreter implementation) to be as fast as Python (www.bagley.org/~doug/shootout). Even if the next Ruby interpreter (re-written from scratch and called Rite) seems to be very very promissing, this is a fact.
Moreover, even if Ruby is quite old (1993 for the first release), it is a rather young language outside Japan. The community is growing every day and more and more people talk about it but the fact is that it does not have the impact (in the Web for example) of Python or Perl for example.
Ruby and Python are direct concurrent languages and some say that as Python is "more" advanced in terms of libraries, usability and managability, Ruby is not worth looking at.
Ruby lacks some features, which has personnally annoyed me: lack of class destructors or any mechanism of this kind (you can use block arguments but in my opinion it is not really a clever and usable solution in many cases), no cool GUI framework that would come with a good (and English) documentation, currently no easy and satisfying possibility to build an executable from a Ruby script (it simply prevents the Ruby script from being used in professional company packages; which company would accept that its clients would have to install Ruby to run part of a software, for me all the necessary script files should make a simple exe file),...
Moreover, we must not forget that this kind of Very High Level programming language ('High' in terms of relative distance to the computer) cannot be appropriately used in all cases and cannot be compared with languages like C or C++ in terms of system programming, for example.
Links.
Ruby main page site
Ruby application/library archives list
A Ruby portal
Another Ruby portal
The first book on Ruby online (very good)
A Windows Ruby installer
A Ruby page
The Ruby newsgroups archive (very interesting!)
Very dynamic newsgroup
Conclusion.
I hope that this little introduction was good enough to give you the desire to look a little bit at this language. Even if you do not choose to go further and deeper into it, it really contains some interesting ideas that make it worth spending at least a couple of weeks with.
If you want to give me feedback on this: wiss1976@yahoo.fr.
Happy coding.